/*
 *   The contents of this file are subject to the Mozilla Public License
 *   Version 1.1 (the "License"); you may not use this file except in
 *   compliance with the License. You may obtain a copy of the License at
 *   http://www.mozilla.org/MPL/
 *
 *   Software distributed under the License is distributed on an "AS IS"
 *   basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 *   License for the specific language governing rights and limitations
 *   under the License.
 *
 *   The Original Code is Matra - the DTD Parser.
 *
 *   The Initial Developer of the Original Code is Conrad S Roche.
 *   Portions created by Conrad S Roche are Copyright (C) Conrad 
 *   S Roche. All Rights Reserved.
 *
 *   Alternatively, the contents of this file may be used under the terms
 *   of the GNU GENERAL PUBLIC LICENSE Version 2 or any later version
 *   (the  "[GPL] License"), in which case the
 *   provisions of GPL License are applicable instead of those
 *   above.  If you wish to allow use of your version of this file only
 *   under the terms of the GPL License and not to allow others to use
 *   your version of this file under the MPL, indicate your decision by
 *   deleting  the provisions above and replace  them with the notice and
 *   other provisions required by the GPL License.  If you do not delete
 *   the provisions above, a recipient may use your version of this file
 *   under either the MPL or the GPL License."
 *
 *   [NOTE: The text of this Exhibit A may differ slightly from the text of
 *   the notices in the Source Code files of the Original Code. You should
 *   use the text of this Exhibit A rather than the text found in the
 *   Original Code Source Code for Your Modifications.]
 *
 * Created: Conrad S Roche <derupe at users.sourceforge.net>,  25-Jul-2000
 * 
 * Earlier named ParseDTDEg.java, renamed to Matra.java on 16-Aug-2003
 */
package com.conradroche.matra;

import java.util.*;
import java.io.*;

import com.conradroche.matra.dtdparser.DTDParser;

import com.conradroche.matra.exception.DTDException;

import com.conradroche.matra.io.DTDFile;
import com.conradroche.matra.io.DTDSource;
import com.conradroche.matra.io.DTDUrl;

import com.conradroche.matra.tree.DTDTree;

/**
 * Command line processor for the Matra DTD parser.
 * 
 * @author Conrad Roche
 */
public class Matra {
	
	/**
	 * The version number for this release of Matra.
	 */
	public static final String version = "0.8.2b";
	
	/**
	 * Value indicating that the location specified
	 * is a URL.
	 */
	private static final int LOCATION_URL = 1;
	
	/**
	 * Value indicating that the location specified
	 * is a local file.
	 */
	private static final int LOCATION_FILE = 2;
	
	/**
	 * Value indicating that the location specified
	 * is a local file containing a list of files.
	 */
	private static final int LOCATION_LIST = 3;
	
	/**
	 * Value indicating that the location specified
	 * is the DTD String.
	 */
	private static final int LOCATION_STRING = 4;
	
	/**
	 * Value indicating that the location was not specified.
	 */
	private static final int LOCATION_NOT_SPECIFIED = -1;
	
	/**
	 * Flag to determine if the output is verbose.
	 */
	private boolean verbose = false;
	
	/**
	 * Flag to determine if the DTD tree is
	 * to be displayed. 
	 */
	private boolean showTree = false;
	
	/**
	 * Flag to determine if the merged DTD
	 * is to be displayed.
	 */
	private boolean showMerge = false;
	
	/**
	 * Flag determining what kind of location
	 * is specified for the DTD.
	 */
	private int locationType = -1;


	/**
	 * The location of the DTD.
	 */
	private String location = null;
		
	/**
	 * Matra constructor comment.
	 */
	public Matra() {
		super();
	}

	/**
	 * Main method for the Matra command-line
	 * processor. Allows you to process a DTD
	 * resource.
	 * 
	 * @param args Command line arguments.
	 */
	public static void main(String[] args) {
	
		if(args.length == 0) {
			showUsage();
			return;
		}

		Matra matra = new Matra();		
		matra.readCmdLineOptions(args);

		DTDParser parser = new DTDParser();
		/*
		 * DTDParser: 
		 * 
		 * This is the main parsing class. Calling apps will be 
		 * invoking this class directly to do the DTD Parsing; they
		 * should not call any of the other parsing classes directly.
		 * 
		 * There are two methods to parse the DTD.
		 * 
		 * parse(String)
		 *    If you have the DTD available as a String, then you can 
		 *    pass that string to this method.
		 * 
		 * parse(DTDSource)
		 *    If you have the DTD in a file or on the internet, then
		 *    pass a DTDSource object containing the file location or 
		 *    URL of the DTD file. 
		 * 
		 * The above parse methods will, obviously, parse the DTD. If
		 * it encounters any problem - say, if the file could not be
		 * read or if it has a syntax error - it will throw a DTDException.
		 * 
		 * There is one more method of interest in the DTDParser
		 * 
		 * DocType getDocType()
		 *    This method will return the "DocType" - the parsed DTD 
		 *    object. Call this method only after a successful parse;
		 *    if not, you'll get a DTDException.
		 */
		 
		/*
		 * DTDSource:
		 * 
		 * If you do not have the DTD as a String object, you'll need to 
		 * create a DTDSource object. This object will help the DTDParser
		 * read the DTD - taking that burden off the application.
		 * 
		 * As of now, you have two kinds of DTDSource:
		 * 
		 * DTDFile:
		 * 
		 *    Use This if the DTD is located in a local file.
		 * 
		 *    Use the constructor DTDFile(filelocation)
		 * 
		 *    Where filelocation contains both the path and the name of 
		 *    the file.
		 * 
		 * DTDUrl:
		 * 
		 *    Use this if the DTD is located on the internet. Make sure
		 *    that, either you are the owner of that URL or that the
		 *    owner doesn't have an issue with you connecting to the
		 *    site and downloading the DTD using automated means.
		 *  
		 *    Use the constructor DTDUrl(url)
		 * 
		 *    Where url is the complete URL for the DTD location.
		 */
		 
		/*
		 * DTDTree:
		 * 
		 * The DTDTree is the tree generator for the DTD. It will display
		 * the structure of the DTD in a simplified tree format. Its a great 
		 * way to learn the structure of a mew DTD. NOTE: It *simplifies* the
		 * structure of the DTD for a quick visual overview, the actual
		 * structure that the DTD describes could be more complex. It just 
		 * looks at the parent-child relationships between the Element Types
		 * and not the complex content specs. After getting a high level 
		 * overview using the generated tree, go through the dtd to understand 
		 * it more fully.  
		 */
	
		try {
	
			DTDSource dtdSrc;
			
			switch (matra.locationType) {

				case LOCATION_FILE :
					
					dtdSrc = new DTDFile(matra.location);
					matra.process(dtdSrc);
					break;
	
				case LOCATION_URL :
					
					dtdSrc = new DTDUrl(matra.location);
					matra.process(dtdSrc);
					break;
	
				case LOCATION_STRING :
					matra.display("=========== Parsing DTD String passed ===============");
					parser.parse(matra.location);
	
					//Print the Tree
					matra.displayTree(parser);
							
					//Print the merged DTD
					matra.displayMerge(parser);
					break;
					
				case LOCATION_LIST :
					matra.parseDTDFiles(matra.location);
					break;
					
				default :
					showUsage();
					return;
	
			}		

		} catch(DTDException e ) {
			
			System.out.println("\n\nCaught DTDException - " + (matra.verbose ? "" : e.getMessage()));
			if(matra.verbose) {
				e.printStackTrace();
			} 
		} catch(Throwable th ) {
			
			System.out.println("\n\nCaught Exception - " + 
				(matra.verbose ? "" : th.getClass().getName() + 
					(th.getMessage() != null ? ": " + th.getMessage() : "")));
			if(matra.verbose) {
				th.printStackTrace();
			} 
		}
		
				
		if(!matra.verbose && !matra.showMerge && !matra.showTree) {
			//display success message, if nothing was displayed until now.
			if(matra.locationType == LOCATION_STRING) {
				System.out.println("\n\nCompleted parsing the DTD string - \"" + matra.location + "\".");
			} else {
				System.out.println("\n\nCompleted parsing the DTD resource - " + matra.location + ".");
			}
		}
	}

	/**
	 * Process the specified DTD Data Source.
	 * 
	 * @param dtdSrc The DTDSource to parse & process.
	 * @throws DTDException If the source could not be read or 
	 * 			had invalid data.  
	 */
	private void process(DTDSource dtdSrc) throws DTDException {
		
		DTDParser parser = new DTDParser();
		
		display("=========== Parsing DTD  " + location + " ===============");
		parser.parse(dtdSrc);

		//Print the Tree
		displayTree(parser);
						
		//Print the merged DTD
		displayMerge(parser);
	}
	
	/**
	 * Display the DTD Tree for the parsed data.
	 * 
	 * @param parser
	 * @throws DTDException If the dtd is not completely parsed yet OR
	 * 			if any exception is encountered while generating the tree.
	 */
	private void displayTree(DTDParser parser) throws DTDException {

		if(showTree) {		
			DTDTree tree = new DTDTree(parser);
			tree.printTrees();
		}
	}
	
	/**
	 * Display the merged dtd for the parsed data.
	 * 
	 * @param parser
	 * @throws DTDException If the dtd is not completely parsed yet.
	 */
	private void displayMerge(DTDParser parser) throws DTDException {

		if(showMerge) {		
			display("\n=========== Merged DTD ===============");
			System.out.println((parser.getDocType().toString())); //this will be deprecated! 
		}
	}
	
	/**
	 * Reads the list file and parses all the files mentioned in it.
	 * 
	 * @param filename The location of the list file.
	 * 
	 * @throws DTDException If the list file could not be read. 
	 */
	public void parseDTDFiles(String filename) throws DTDException {
	
		String file;
		DTDParser parser = new DTDParser();
	
		Enumeration files = getFileList(filename);
		DTDFile dtdFile;
	
		while(files.hasMoreElements()) {
			file = (String) files.nextElement();

			try {
				dtdFile = new DTDFile(file);
				display("=========== Parsing DTD  " + file + " ===============");
				parser.parse(dtdFile);
				
				//Print the Tree
				if(showTree) {
					DTDTree tree = new DTDTree(parser);
					tree.printTrees();
				}
							
				//Print the merged DTD
				if(showMerge) {
					display("\n=========== Merged DTD ===============");
					System.out.println(parser.getDocType().toString()); //this will be deprecated! 
				}
			} catch(Throwable th) {
				System.out.println("\n\nCaught Exception while parsing file \"" + file + "\"- " + (verbose ? "" : th.getMessage()));
				if(verbose) {
					th.printStackTrace();
				} 
			}
		}
	}

	/**
	 * Method to retrieve the list of files to be validated.
	 * 
	 * @param fileName  The location of the list file.
	 * 
	 * @return An Enumeration of file names listed in the file specified.
	 * 
	 * @throws DTDException If the list file could not be read.
	 */
	private Enumeration getFileList(String fileName) throws DTDException {
	
		Vector fileList = new Vector();
	
		try {
			BufferedReader in = new BufferedReader( new InputStreamReader( new BufferedInputStream( 
									new FileInputStream(fileName) ) ) );
			
			String line, file;
			
			while((line = in.readLine())!= null) {
				file = line.trim();
				if(file.length() != 0)
					fileList.addElement(line);
			}
				
			in.close();
	
			return fileList.elements();
		}
		catch (FileNotFoundException e) {
//			e.printStackTrace();
			throw new DTDException("File " + fileName + " not found");
		}
		catch (IOException e) {
//			e.printStackTrace();
			throw new DTDException("Unable to read File " + fileName);
		}
	}

	/**
	 * Displays the command line options.
	 */
	private static void showUsage() {
		
		String className = Matra.class.getName();
		//CR: TODO: Move this to a file
		
		String usage = 
		"NAME\n\t" +
			"Matra - parse the DTD; display the merged DTD or the DTD Tree if specified.\n\n" +

		"SYNOPSIS\n\t" +

			"Matra [-v] [-help] [-merge] [-tree] [-f file | -u url | -l file | -s string]\n\n" +

		"OPTIONS\n\n\t" +

			"-f file		The <file> is a local DTD file.\n\t" +
			"-help		Display this help message.\n\t" +
			"-l file		The <file> is a local file containing a list of DTDs.\n\t" +
			"-merge		Display the merged DTD.\n\t" +
			"-s string	The <string> is the DTD passed as a String.\n\t" +
			"-tree		Display the DTD in a simple Tree format.\n\t" +
			"-u url		The <url> is a URL pointing to a DTD.\n\t" +
			"-v		Verbose mode\n\n" +

		"EXAMPLES\n\n\t" +

			"Example 1: Parse a DTD file to check for syntax errors\n\n\t" +

			className + " -f c:\\path\\filename.dtd\n\n\t" +

			"Example 2: Parse a DTD file, display the tree and merged dtd\n\n\t" +

			className + " -merge -tree -f c:\\path\\filename.dtd\n\n\t" +

			"Example 3: Parse a DTD whose location is specified by a URL.\n\n\t" +

			className + " -u http://host/path/name.dtd";


		System.out.println(usage);
	}
	
	/**
	 * Reads the command line options sent and 
	 * modifies the programs behavious accordingly.
	 * 
	 * @param args The command line arguments.
	 */
	private void readCmdLineOptions(String[] args) {
		
		if(args.length == 0) {
			showUsage();
			System.exit(1);
		}

		//the first arg has to be an option and  not a value
		
		int i = 0; //the current arg being checked
		
		while(i < args.length) {
			
			String option = args[i++];
//			i++;
			 
			if(option.equalsIgnoreCase("-v")) { //verbose
				verbose = true;
				continue;
			} else if(option.equalsIgnoreCase("-tree")) { //show tree
				showTree = true;
				continue;
			} else if(option.equalsIgnoreCase("-merge")) { 
				showMerge = true;
				continue;
			} else if(option.equalsIgnoreCase("-help")) {
				showUsage();
				System.exit(1); 
			} else if(option.equalsIgnoreCase("-f")) {
				locationType = LOCATION_FILE;
			} else if(option.equalsIgnoreCase("-u")) {
				locationType = LOCATION_URL; 
			} else if(option.equalsIgnoreCase("-s")) {
				locationType = LOCATION_STRING;
			} else if(option.equalsIgnoreCase("-l")) {
				locationType = LOCATION_LIST;
			} else {
				display("Invalid option encountered - '" + option + "'\n");
				showUsage();
				System.exit(1);
			}

			if(i >= args.length || location != null) {
				showUsage();
				System.exit(1);
			}
			location = args[i++];
			if(locationType == LOCATION_FILE || locationType == LOCATION_LIST) {
				checkIfRelativePath();
			}
//			i++;
		}
	}

	/**
	 * Converts a relative path location
	 * to an absolute path.
	 *
	 */
	private void checkIfRelativePath() {
		
		if(location.indexOf(":") != -1) {
			//for dos based system, this would imply absolute path
			return;
		}
		
		if(location.startsWith("\\\\")) {
			//UNC Path
			return;
		}
		
		String separator = System.getProperty("file.separator");
		
		if(separator.equals("/")) {
			if(location.startsWith("/")) {
				//for unix systems, this would imply absolute path
				return;
			} else {
				location = System.getProperty("user.dir") + separator + location;
			}
		} else {
			location = System.getProperty("user.dir") + separator + location;
		}
		
		//CR: TODO: Check if any more checks need to be made
		
		//CR: TODO: what about "\temp\test.dtd"?
	}
	
	/**
	 * Displays the message if verbose mode is on.
	 *
	 * @param message The message to be displayed.
	 */
	private void display(String message) {
		if(verbose) {
			System.out.println(message);
		}
	}
}